﻿using System;
using System.Collections.Generic;
using System.Linq;
using NRank.Algorithms.Strategies;
using NRank.Common;

namespace NRank.Benchmark.Queries
{
	/// <summary>
	/// Implements benchmark query: Two joins (Towns join Clubs join Hotels)
	/// </summary>
	public class DoubleJoinQuerySet : BenchmarkQuerySet
	{
		/// <summary>
		/// Result function for first join
		/// </summary>
		private static readonly Func<Town, Club, JoinResult> resultFunc =
			(t, c) => new JoinResult {IdClub = c.IdClub, IdTown = t.IdTown, Rating = t.Rating + c.Rating};

		/// <summary>
		/// Result function for second join
		/// </summary>
		private static readonly Func<IRanked<JoinResult>, Hotel, JoinResult> doubleJoinFunc =
			(j, h) => new JoinResult {IdClub = j.Object.IdClub, IdHotel = h.IdHotel, IdTown = j.Object.IdTown, Rating = j.Object.Rating + h.Stars};

		protected override IEnumerable<IRanked<JoinResult>> HashRankJoinRoundRobinTest(NRankBenchmarkDbDataContext cx)
		{
			return cx.Towns
				.OrderByDescending(t => t.Rating)
				.HashRankJoin(
					cx.Clubs.OrderByDescending(c => c.Rating),
					t => t.Rating,
					c => c.Rating,
					t => t.IdTown,
					c => c.IdTown, resultFunc, PullingStrategyTypes.Alternating)
				.HashRankJoin(cx.Hotels.OrderByDescending(h => h.Stars),
				              j => j.Rank,
				              h => h.Stars,
				              j => j.Object.IdTown, h => h.IdTown, doubleJoinFunc, PullingStrategyTypes.Alternating);
		}

		protected override IEnumerable<IRanked<JoinResult>> HashRankJoinAdaptiveTest(NRankBenchmarkDbDataContext cx)
		{
			return cx.Towns
				.OrderByDescending(t => t.Rating)
				.HashRankJoin(
					cx.Clubs.OrderByDescending(c => c.Rating),
					t => t.Rating,
					c => c.Rating,
					t => t.IdTown,
					c => c.IdTown,
					resultFunc,
					PullingStrategyTypes.MinimizeThreshold)
				.HashRankJoin(
					cx.Hotels.OrderByDescending(h => h.Stars),
					j => j.Rank,
					h => h.Stars,
					j => j.Object.IdTown,
					h => h.IdTown,
					doubleJoinFunc,
					PullingStrategyTypes.MinimizeThreshold);
		}

		protected override IEnumerable<IRanked<JoinResult>> RankJoinTestRoundRobin(NRankBenchmarkDbDataContext cx)
		{
			return cx.Towns
				.OrderByDescending(t => t.Rating)
				.RankJoin(
					cx.Clubs.OrderByDescending(c => c.Rating),
					t => t.Rating,
					c => c.Rating,
					resultFunc,
					(t, c) => t.IdTown == c.IdTown,
					PullingStrategyTypes.Alternating)

				.RankJoin(
					cx.Hotels.OrderByDescending(h => h.Stars),
					j => j.Object.Rating,
					h => h.Stars,
					doubleJoinFunc,
					(j, h) => j.Object.IdTown == h.IdTown,
					PullingStrategyTypes.Alternating
				);
		}

		protected override IEnumerable<IRanked<JoinResult>> RankJoinTestAdaptive(NRankBenchmarkDbDataContext cx)
		{
			return cx.Towns
				.OrderByDescending(t => t.Rating)
				.RankJoin(
					cx.Clubs.OrderByDescending(c => c.Rating),
					t => t.Rating,
					c => c.Rating,
					(t, c) => new JoinResult {IdClub = c.IdClub, IdTown = t.IdTown},
					(t, c) => t.IdTown == c.IdTown,
					PullingStrategyTypes.MinimizeThreshold)
				.RankJoin(
					cx.Hotels.OrderByDescending(h => h.Stars),
					j => j.Object.Rating,
					h => h.Stars,
					doubleJoinFunc,
					(j, h) => j.Object.IdTown == h.IdTown,
					PullingStrategyTypes.MinimizeThreshold
				);
		}

		protected override IEnumerable<IRanked<JoinResult>> NestedLoopJoinTest(NRankBenchmarkDbDataContext cx)
		{
			return cx.Clubs.OrderByDescending(c => c.Rating)
				.NestedLoopRankJoin(
					cx.Towns,
					c => c.Rating,
					t => t.Rating,
					(c, t) => new JoinResult {IdClub = c.IdClub, IdTown = t.IdTown},
					(c, t) => c.IdTown == t.IdTown)
				.NestedLoopRankJoin(
					cx.Hotels,
					j => j.Rank,
					h => h.Stars,
					doubleJoinFunc,
					(j, h) => h.IdTown == j.Object.IdTown);
		}

		protected override IEnumerable<IRanked<JoinResult>> NestedLoopJoinEqTest(NRankBenchmarkDbDataContext cx)
		{
			return cx.Clubs.OrderByDescending(c => c.Rating)
				.NestedLoopRankJoin(
					cx.Towns,
					c => c.Rating,
					t => t.Rating,
					(c, t) => new JoinResult {IdClub = c.IdClub, IdTown = t.IdTown}, 
					c => c.IdTown,
					t => t.IdTown)
				.NestedLoopRankJoin(
					cx.Hotels,
					j => j.Rank,
					h => h.Stars,
					doubleJoinFunc,
					j => j.Object.IdTown,
					h => h.IdTown);
		}

		protected override IEnumerable<IRanked<JoinResult>> TraditionalQuery(NRankBenchmarkDbDataContext cx)
		{
			return cx.Towns
				.Join(cx.Clubs,
				      t => t.IdTown,
				      c => c.IdTown,
				      (t, c) => new {c.IdClub, t.IdTown, Rating = c.Rating + t.Rating})
				.Join(cx.Hotels, n => n.IdTown, h => h.IdTown,
				      (n, h) => new {n.IdClub, n.IdTown, Rating = n.Rating + h.Stars})
				.OrderByDescending(n => n.Rating)
				.AsEnumerable()
				.Select(n => new JoinResult {IdClub = n.IdClub, IdTown = n.IdTown, Rating = n.Rating})
				.AsRanked(n => n.Rating);
		}
	}
}